package com.googlecode.entity_dao.core.dao.hibernate;

import java.io.Serializable;
import java.util.List;

import org.hibernate.Criteria;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.criterion.Projections;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;

import com.googlecode.entity_dao.core.criteria.PersistentEntityCriteria;
import com.googlecode.entity_dao.core.entity.Persistable;

/**
 * Base class for persistent entity DAO implementations providing data access through JPA / Hibernate persistence APIs.
 * 
 * @see Persistable
 * 
 * @author mauro.franceschini
 */
public abstract class BaseHibernateDataAccessor {
    protected static final Logger LOGGER = LoggerFactory.getLogger(BaseHibernateDataAccessor.class);

    private SessionFactory sessionFactory;

    @Autowired
    public void setSessionFactory(SessionFactory sessionFactory) {
        this.sessionFactory = sessionFactory;
    }

    /**
     * Returns an open {@link SessionFactory} instance providing access to the Hibernate {@link Session}.
     * 
     * <p>
     * 
     * The most convenient method implementation pattern is to rely on entity manager instance injection via the
     * {@link Autowired} annotation within a Spring application context.
     * 
     * @return Open {@link SessionFactory} instance.
     */
    protected SessionFactory getSessionFactory() {
        return sessionFactory;
    }

    /**
     * Metodo di utilità per recuperare la <tt>Session</tt> corrente
     * 
     * @return la {@link Session} corrente
     */
    protected final Session getSession() {
        return getSessionFactory().getCurrentSession();
    }

    /**
     * Metodo di utilità per recuperare un nuovo <tt>Criteria</tt> collegato alla <tt>Session</tt> corrente.
     * 
     * @param entityClass la classe {@link Persistable} per cui recuperare il {@link Criteria}
     * @return l'istanza di {@link Criteria} ottenuta dalla {@link Session}
     */
    protected final Criteria getHibernateCriteria(Class<? extends Persistable<? extends Serializable>> entityClass) {
        return getSession().createCriteria(entityClass);
    }

    /**
     * Metodo di utilità per contare il numero di righe restituito da un <tt>Criteria</tt>.
     * <p>
     * Il <tt>Criteria</tt> non deve contenere dei vincoli di paginazione in quanto il metodo esegue il conteggio.
     * 
     * @param criteria l'istanza di {@link Criteria} per cui contare le rige
     * @return il numero di righe restituite dal {@link Criteria} specificat
     */
    protected final int rowCount(Criteria criteria) {
        criteria.setProjection(Projections.rowCount());

        List<?> projectionResults = criteria.list();
        int rowCount = 0;

        Object firstResult = projectionResults.get(0);
        if (projectionResults.size() != 1 || !Number.class.isAssignableFrom(firstResult.getClass())) {
            LOGGER.warn("rowCount projection for the given criteria did not result a single numeric value, returning zero - did you add unnecessary paging constraints to the criteria?");
        } else {
            rowCount = Number.class.cast(firstResult).intValue();
        }

        return rowCount;
    }

    /**
     * Restituisce il <tt>Criteri</tt> che corrisponde ai vincoli esplicitiati nel {@link PersistentEntityCriteria}.
     * 
     * @param entityCriteria i vincoli
     * @param entityClass la classe per cui cercare
     * @return il {@link Criteria} di hibernate
     */
    protected Criteria getCriteria(PersistentEntityCriteria entityCriteria,
            Class<? extends Persistable<? extends Serializable>> entityClass) {
        Criteria criteria = getHibernateCriteria(entityClass);
        entityCriteria.apply(criteria);

        return criteria;
    }
}
